Skip to content

Conversation

@diegomrsantos
Copy link

@diegomrsantos diegomrsantos commented Dec 13, 2025

Closes #431

Adds a new example crate commonware-revm (examples/revm) demonstrating how to wire threshold-simplex consensus into block execution with an EVM.

  • Runs a single-process N-node simulation using commonware_runtime::tokio + commonware_p2p::simulated.
  • simplex orders opaque digests; full blocks are delivered out-of-band and verified by re-execution.
  • Executes blocks with alloy-evm / revm using a QMDB-backed DatabaseAsync adapter bridged via WrapDatabaseAsync + CacheDB overlay.
  • Persists finalized state changes to QMDB in batched writes (accounts, storage, code partitions).
  • Computes a deterministic 32-byte rolling state_root commitment (not an Ethereum MPT) and rejects proposals that do not match.
  • Bonus: hashes the threshold-simplex certificate seed signature to 32 bytes, injects it as block.prevrandao, and exposes it via a stateful precompile at 0x00000000000000000000000000000000000000ff.

Run:
cargo run -p commonware-revm-chain --release -- --nodes 4 --blocks 5 --seed 1

Test:
cargo test -p commonware-revm

@clabby
Copy link
Collaborator

clabby commented Dec 13, 2025

Haven't read into this deeply just yet, but awesome to see someone doing this on a lower-level than communicating with ETH ELs over the engine API :)

Would be really neat to add a revm::Database implementation that's backed by commonware_storage::qmdb.

@patrick-ogrady
Copy link
Contributor

Would be really neat to add a revm::Database implementation that's backed by commonware_storage::qmdb.

This would be :100000000000:

@diegomrsantos diegomrsantos force-pushed the feat/revm-chain-example branch from 3c3680e to 20ed6d2 Compare December 14, 2025 23:07
@diegomrsantos diegomrsantos marked this pull request as draft December 14, 2025 23:22
@diegomrsantos
Copy link
Author

diegomrsantos commented Dec 15, 2025

Haven't read into this deeply just yet, but awesome to see someone doing this on a lower-level than communicating with ETH ELs over the engine API :)

Would be really neat to add a revm::Database implementation that's backed by commonware_storage::qmdb.

Thanks for the quick feedback. I really appreciate the suggestion. Agree that a QMDB-backed backend would be a great “Commonware-native” next step.

One caveat I’ve been thinking about: revm::Database is a synchronous, hot-path interface, while commonware_storage::qmdb is async (and REVM’s built-in async→sync adapter is Tokio-based). I don’t want to accidentally introduce blocking in the EVM execution loop or pull Tokio in outside runtime.

A direction that seems workable is a layered backend: keep a CacheDB/InMemoryDB overlay for sync reads during execution, and add explicit async load_from_qmdb / flush_to_qmdb at finalized block boundaries (so QMDB provides persistence, but the EVM still reads from an in-memory cache).

Does that match what you had in mind, or is there a better pattern you’d prefer for bridging the async/sync boundary here?

@diegomrsantos
Copy link
Author

I'm sorry for the big PR. I know it's not ideal and hard to review. I split it into many small commits to make it more manageable. Please let me know if I should break it into smaller PRs.

@diegomrsantos diegomrsantos marked this pull request as ready for review December 15, 2025 18:37
Add a minimal execution layer (EthEvmBuilder + transact_raw) that converts REVM EvmState diffs into deterministic StateChanges, updates the rolling StateRoot, and commits via DatabaseCommit. Includes a single ETH transfer unit test using InMemoryDB.
Introduce a minimal application/mailbox that implements consensus::simplex Automaton/Relay/Reporter over opaque Sha256 digests, builds placeholder blocks, and reuses the alloy-evm execution path when data is present. Also add a shared EVM env helper (chain_id + block env fields).
Teach the simplex mailbox to broadcast full encoded blocks out-of-band via a p2p Sender when a proposal digest is accepted. The application now ingests received block bytes, queues verify requests until the block arrives, and verifies proposals by re-executing against the parent state and checking state_root.
Wire the simulated network to run N threshold-simplex nodes with out-of-band block broadcast and re-execution verification. Add a deterministic smoke test that finalizes a few blocks and checks all nodes converge on the same head, state_root, and transfer balances.
Persist a 32-byte seed hash per finalized/notarized digest, use it as the next block's prevrandao, and assert seed convergence in the deterministic sim.
Install a stateful precompile at 0x…00ff that returns block.prevrandao, and add unit tests for direct calls and contract-based access.
README now documents the prevrandao/seed flow and the seed precompile address; the CLI prints the finalized head, state root, seed and balances.
Refactor the EVM execution tests with explicit prepare/execute/assert structure and small helpers for funding and nonce retrieval.
Split the large event loop into a small dispatcher and concept-level handlers (genesis/propose/verify/broadcast/report + control queries), keeping behavior unchanged.
Extract helpers for channel registration, application startup, block forwarding, and simplex engine config to make node bootstrap easier to read.
Rename the stream-select enum from Event::{Ingress,Control} to Input::{Consensus,Control} to better reflect the source of messages.
Rename consensus mailbox messages to ConsensusRequest and application handle messages to ApplicationRequest, and update the application actor wiring for clearer concepts.
@diegomrsantos
Copy link
Author

I'm sorry for the big PR. I know it's not ideal and hard to review. I split it into many small commits to make it more manageable. Please let me know if I should break it into smaller PRs.

Nah, this is a great pass (something I've wanted to do for some time). Would you mind just renaming the example to revm?

Of course, it's better indeed.

@diegomrsantos diegomrsantos changed the title [examples/revm_chain] Add REVM chain example on threshold-simplex [examples/revm] Add REVM example on threshold-simplex Dec 23, 2025
@patrick-ogrady
Copy link
Contributor

Given commonware-runtime has both deterministic (simulation/testing) and tokio (production) dialects, do you think it’s acceptable for the QMDB-backed example to require the tokio dialect? Or would you prefer we keep the default deterministic path and do persistence via explicit async load/flush at block/finalization boundaries (so REVM stays sync)?

All our examples use the tokio-runtime (our deterministic runtime is really just for testing).

Can we do async load at the boundaries and have it still work? Seems like you won't know the keys it will touch a priori?

@diegomrsantos
Copy link
Author

Given commonware-runtime has both deterministic (simulation/testing) and tokio (production) dialects, do you think it’s acceptable for the QMDB-backed example to require the tokio dialect? Or would you prefer we keep the default deterministic path and do persistence via explicit async load/flush at block/finalization boundaries (so REVM stays sync)?

All our examples use the tokio-runtime (our deterministic runtime is really just for testing).

I believe you mean testing the lib and the examples aren't included? I initially used the deterministic runtime to test this example, but I will use a different approach then.

Can we do async load at the boundaries and have it still work? Seems like you won't know the keys it will touch a priori?

You're right. I'll work on a proposal using Tokio. Thanks a lot for the feedback!

@diegomrsantos
Copy link
Author

diegomrsantos commented Jan 6, 2026

Hey @patrick-ogrady, quick question: is it expected to use the tokio runtime with commonware_p2p::simulated?

When I run the sim in the example, I’m getting bind errors from the simulated network:

  p2p/src/simulated/network.rs:961: BindFailed
  p2p/src/simulated/network.rs:1057: ConnectionFailed

It looks like the simulated network picks a pseudo‑random IP and then actually binds sockets under tokio, which fails on macOS in my environment. Under deterministic runtime this doesn’t happen (since it’s a virtual network).

Is tokio + simulated P2P a supported/expected combo? If not, what’s the preferred pattern for examples that need tokio but still want a simulated network?

@diegomrsantos
Copy link
Author

@patrick-ogrady Quick summary of what has been done:

  • QMDB-backed REVM DB: implement DatabaseAsyncRef on QMDB (accounts/storage/code partitions) and bridge via WrapDatabaseAsync + CacheDB overlay; persistence happens only at finalized boundaries via batched QMDB writes. Storage keys are fixed-length and we bump storage_generation on recreate/selfdestruct to invalidate old slots.
  • Tokio + simulated P2P fix: added simulated::Network::new_with_base_addr and use 127.0.0.1:0 in the example so peers bind real sockets under Tokio; we capture the OS-assigned port instead of using pseudo-random IPs that cannot bind.

Question: should we switch to an authenticated QMDB variant (e.g. qmdb::current) and use its root for the state commitment, or keep the current rolling commitment?

@patrick-ogrady
Copy link
Contributor

patrick-ogrady commented Jan 8, 2026

Using QMDBs root for the state commitment would be HUGE (can use it for state sync/proofs externally).

At that point, I'm a very happy camper.

@diegomrsantos
Copy link
Author

diegomrsantos commented Jan 8, 2026

Using QMDBs root for the state commitment would be HUGE (can use it for state sync/proofs externally).

At that point, I'm a very happy camper.

Given the goal of external state sync/proofs, I propose switching the example to an authenticated QMDB using the ordered current variant (so we can provide inclusion + exclusion proofs and complete range proofs).

Plan:

  1. Replace the unauthenticated qmdb::store::db::Db with qmdb::current::ordered::{fixed,variable}::Db for accounts/storage/code.
  2. Keep the REVM adapter the same shape (DatabaseAsyncRef -> WrapDatabaseAsync -> CacheDB).
  3. After applying finalized changes, use the authenticated store’s root() and set it as the chain’s state_root commitment.
  4. If needed, define a single commitment from multiple partitions via domain‑separated hash of the three roots (accounts/storage/code).

If you’re good with ordered + a combined root, I’ll move forward.

@patrick-ogrady
Copy link
Contributor

Given the goal of external state sync/proofs, I propose switching the example to an authenticated QMDB using the ordered current variant (so we can provide inclusion + exclusion proofs and complete range proofs).

I think that would be the most powerful example, although I've always believed you could get away with any: https://commonware.xyz/blogs/adb-any

In any case, I think it'll be easy to swap out the QMDB as needed (for different variants).

@patrick-ogrady
Copy link
Contributor

Given the goal of external state sync/proofs, I propose switching the example to an authenticated QMDB using the ordered current variant (so we can provide inclusion + exclusion proofs and complete range proofs).

I think that would be the most powerful example, although I've always believed you could get away with any: https://commonware.xyz/blogs/adb-any

In any case, I think it'll be easy to swap out the QMDB as needed (for different variants).

The benefit to using a simpler one at first is that we already have state sync done. current is more complex (and will come later).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[examples] revm Blockchain

3 participants